package org.newlucene.core.search;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.newlucene.core.index.IndexReader;
import org.newlucene.core.index.Term;

public class BooleanSearcher extends IndexSearcher
{
    private int currentDoc = 0;
    private List<IndexSearcher> subSearchers = new ArrayList<IndexSearcher>();
    private List<SubCollector> subCollectors = new ArrayList<SubCollector>();
    private final int bucketTableCapacity = 1024;
    private HashMap<Integer, Bucket> bucketTable = new HashMap<Integer, Bucket>(bucketTableCapacity);
    
    private int requiredMask = 0;
    private int prohibitedMask = 0;
    private int nextMask = 1;
    
    public BooleanSearcher(IndexReader indexReader)
    {
    	super(indexReader);
    }

    private int getMask(boolean required, boolean prohibited)
    {
    	int mask = 0;
    	if (required || prohibited)
    	{
    		if (nextMask == 0)
    		{
    			throw new IndexOutOfBoundsException("More than 32 required/prohibited clauses in query.");
    		}
   			mask = nextMask;
   			nextMask = nextMask << 1;
    	}
    	else
    	{
    		mask = 0;
    	}
    	
    	if (prohibited)
    	{
    		prohibitedMask |= mask; // update prohibited mask
    	}
    	else if (required)
    	{
    		requiredMask |= mask; // update required mask
    	}
    	return mask;
    }
    
    public void add(IndexSearcher searcher, boolean required, boolean prohibited)
    {
    	int mask = getMask(required, prohibited);
    	// add subsearcher
    	subSearchers.add(searcher);
    	// add subcollector
    	SubCollector c = new SubCollector();
    	c.set(bucketTable, mask);
    	subCollectors.add(c);
    }

    public void add(IndexSearcher searcher, SubCollector c, boolean required, boolean prohibited)
    {
    	int mask = getMask(required, prohibited);
    	// add subsearcher
    	subSearchers.add(searcher);
    	// add subcollector
    	c.set(bucketTable, mask);
    	subCollectors.add(c);
    }

    protected void search(HitCollector hc, int maxDoc) throws IOException
    {
        while (currentDoc < maxDoc)
        {
        	bucketTable.clear(); // reset for next round
        	currentDoc = Math.min(currentDoc + bucketTableCapacity, maxDoc);
            for (int i = 0; i < subSearchers.size(); i++)
            {
            	subSearchers.get(i).search(subCollectors.get(i), currentDoc);
            }
            
            // collect hits
            for (Integer doc : bucketTable.keySet())
            {
            	Bucket bucket = bucketTable.get(doc);
            	if ((bucket.bits & prohibitedMask) == 0 && // check prohibited
                		(bucket.bits & requiredMask) == requiredMask) // check required
               		{
                		//float score = bucket.score * boost / subSearchers.size();

            			// changed to allow straight score comparison - Jian
                		float score = bucket.score * boost;
                		hc.collect(doc, score);
               		}            	
            }
        }
    }
    
    public String explain()
    {	
    	// build the keyword string
    	int nextMask = 1;
    	StringBuilder sb = new StringBuilder();
    	for (IndexSearcher subSearcher : subSearchers)
    	{
    		if ((prohibitedMask & nextMask) == nextMask)
    		{
    			sb.append("-");
    		}
    		
    		if ((requiredMask & nextMask) == nextMask)
    		{
    			sb.append("+");
    		}
    		
    		nextMask = nextMask << 1;
    		
    		if (subSearcher instanceof BooleanSearcher)
    		{
    			sb.append("(").append(subSearcher.explain()).append(")");
    		}
    		else
    		{
    			sb.append(subSearcher.explain());
    		}
    		sb.append(" ");
    	}
    	sb.deleteCharAt(sb.length() - 1); // remove the last white space
    	return sb.toString();
    }
}